package cn.virens.common.util.components.queue;

import cn.virens.common.util.core.Assert;
import cn.virens.common.util.exception.APIException;
import org.dromara.hutool.core.date.DateUtil;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class VirQueue<T extends Serializable> {
    protected final Queue<T> queue = new ConcurrentLinkedQueue<>();

    protected final Lock lock = new ReentrantLock();

    protected Consumer<T> callback;

    // 当前操作对象-设置时间
    protected long currentTime = 0;

    // 当前操作对象
    protected T current;

    /**
     * 添加一条数据到队列
     *
     * @param item 数据实体
     * @return 是否成功
     */
    public boolean offer(T item) {
        return queue.offer(item);
    }

    /**
     * 如果当前数据为空 或者 已经超时，就回调超时并重设当前值与时间戳
     *
     * @param timeout 超时时长
     * @param unit    超时单位
     * @return 当前处理的条目
     */
    public Optional<T> peek(long timeout, TimeUnit unit) throws APIException {
        return lock(() -> Optional.ofNullable(initCurrent(timeout, unit)));
    }

    /**
     * 删除队列中匹配的数据条目
     *
     * @param predicate 匹配器
     */
    public Boolean remove(Predicate<T> predicate) throws APIException {
        return lock(() -> removeIf0(predicate));
    }

    /**
     * 判断是否当前数据，否则尝试从队列删除
     *
     * @param <R>      返回类型
     * @param timeout  超时时长
     * @param unit     超时单位
     * @param function 数据判断（返回不为null就是对应的条目）
     * @return 返回处理结果
     */
    public <R> Optional<R> remove(long timeout, TimeUnit unit, Function<T, R> fun) {
        return lock(() -> Optional.ofNullable(remove0(timeout, unit, fun)));
    }

    /**
     * 判断是否为空
     */
    public boolean isEmpty() throws APIException {
        return current == null && queue.isEmpty();
    }

    /**
     * 设置超时数据处理回调
     *
     * @param callback 回调接口
     */
    public void setTimeoutListener(Consumer<T> callback) {
        this.callback = callback;
    }

    /**
     * 判断是否当前数据，否则尝试从队列删除
     *
     * @param <R>      返回类型
     * @param timeout  超时时长
     * @param unit     超时单位
     * @param function 数据判断（返回不为null就是对应的条目）
     * @return 返回处理结果
     */
    protected <R> R remove0(long timeout, TimeUnit unit, Function<T, R> function) {
        if (current != null && !isTimeout(unit.toMillis(timeout))) {
            R result = Assert.isNull(function).apply(current);

            if (result != null && current != null) {
                this.currentTime = 0;
                this.current = null;

                return result;//
            }
        }

        // 从队列中查找符合条件的数据，并删除对应的条目--------------------------------
        for (Iterator<T> itr = queue.iterator(); itr.hasNext(); ) {
            R result = function.apply(itr.next());
            if (result != null) {
                itr.remove();

                return result;//
            }

        }

        return null;
    }

    /**
     * 删除队列中匹配的数据条目
     */
    protected boolean removeIf0(Predicate<T> predicate) throws APIException {
        if (current != null && predicate.test(current)) {
            this.callback.accept(current);
            this.currentTime = 0;
            this.current = null;
        }

        return queue.removeIf(predicate);
    }

    /**
     * 如果当前数据为空 或者 已经超时，就回调超时并重设当前值与时间戳
     *
     * @param timeout 超时时长
     * @param unit    超时单位
     */
    protected T initCurrent(long timeout, TimeUnit unit) throws APIException {
        if (current == null || isTimeout(unit.toMillis(timeout))) {
            if (current != null && callback != null) {
                this.callback.accept(current);
            }

            // 重设时间戳 & 设置当前对象
            this.currentTime = DateUtil.current();
            this.current = queue.poll();
        }

        return current;
    }

    /**
     * 检查是否超时
     */
    protected boolean isTimeout(long offset) throws APIException {
        return currentTime + offset <= DateUtil.current();
    }

    /**
     * 执行锁
     */
    protected <R> R lock(Supplier<R> supplier) {
        this.lock.lock();

        try {
            return supplier.get();
        } finally {
            lock.unlock();
        }
    }

}
